// head file
#include "proxy.h"

// c++ standard lib
#include <iostream>
#include <cerrno>
#include <cstring>
#include <pthread.h>
#include <signal.h>

// linux socket
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

using namespace std;

void Proxy::Error(const char* message)
{
    cout << message << strerror(errno) << "(errno: " << errno << ")" << endl;
	close(serverSocket);
	exit(-1);
}

bool Proxy::InitServerSocket()
{
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) // linux socket fail created
    {
        cout << "Server create udp socket error" << strerror(errno)
            << "(errno: )" << errno << ")"
            << endl;
		return false;
    }

    Server.sin_family = AF_INET;
    Server.sin_port = htons(LISTEN_PORT);
    Server.sin_addr.s_addr = INADDR_ANY;

    // bind
    if (bind(serverSocket, (struct sockaddr*)&Server, sizeof(sockaddr)) == -1)
    {
        Error("Proxy server bind error: ");
        return false;
    }

    if (listen(serverSocket, SOMAXCONN) == -1)
    {
        Error("Listen error: ");
        return false;
    }
    return true;
}

int Proxy::RecvHTTPRequest(int s, char* buf, int bufSize)
{
    int len = 0;
    char* prf = buf;
    while (len < bufSize)
    {
        int ret = recv(s, buf, bufSize - len, 0);
        if (ret > 0) {
            len += ret;
        } else {
            return ret;
        }

        // Find end tag: \r\n\r\n
        if (len > 4)
        {
            if (strstr(prf, "\r\n\r\n")) break;
            else {
                prf = buf + len - 4;
            }
        }
    }
    return len;
}

int Proxy::InitWebServerHost(int* s, char* HostName, int Port)
{
    sockaddr_in Server;
    Server.sin_family = AF_INET;
    Server.sin_port = htons(Port);

    hostent* hostent = gethostbyname(HostName);
    in_addr inad = *((in_addr*)*hostent->h_addr_list);
    Server.sin_addr.s_addr = inad.s_addr;

    *s = socket(AF_INET, SOCK_STREAM, 0);
    if (*s == -1) {
        Error("create web server socket error: ");
        return -1;
    }

    if (connect(*s, (const sockaddr*)&Server, sizeof(Server)) == -1)
    {
        printf("fail to connect to web server\n");
        close(*s);
        return 0;
    }
    cout << "CONNECT " << HostName << ":" << Port << endl;
    return 1;
}

int Proxy::ConnectToServer(int& s, char* recvBuf, int len)
{
    // resolve host and port from recvBuf
    char host[MAX_HOST_NAME] = { 0 };
    char strPort[8] = { 0 };
    int port = DEFAULT_PORT;
    printf("scannnnn: %s", recvBuf);

    // sp: " HTTP/1.1"
    char* sp = (char*)(memchr(recvBuf + 8, ' ', len - 8));
    if (!sp) return -1;

    // pt: ":443 HTTP/1.1"
    char* pt = (char*)(memchr(recvBuf + 8, ':', len - 8 - strlen(sp)));

    if (pt)
    {
        int portLen = strlen(pt) - strlen(sp) - 1;
        if (portLen >= 8) return -1;
        memcpy(strPort, pt + 1, portLen);
        port = atoi(strPort);
        memcpy(host, recvBuf + 8, strlen(recvBuf) - 8 - strlen(pt));
    }
    else
    {
        memcpy(host, recvBuf + 8, strlen(recvBuf) - 8 - strlen(pt));
    }

    return InitWebServerHost(&s, host, port) - 1;
}

int Proxy::SendData(int s, const char* buf, int bufSize)
{
    int pos = 0;
    while (pos < bufSize)
    {
        int ret = send(s, buf + pos, bufSize - pos, 0);
        if (ret > 0)
        {
            pos += ret;
        }
        else {
            return ret;
        }
    }
    return pos;
}

int Proxy::PreResponse(int client)
{
    printf("PRe response\n");
    const char response[] = "HTTP/1.1 200 Connection established\r\n"
		"Proxy-agent: One Proxy Lite /0.2\r\n\r\n";
    int ret = SendData(client, response, sizeof(response) - 1);
    if (ret <= 0) return -2;
    return 0;
}

void* Proxy::excThread(void* peer)
{
    Peer* p;
    p = (struct Peer*)peer;
    char buf[MAX_SIZE];

    while (true)
    {
        int ret = recv(p->protocol->webServer, buf, MAX_SIZE, 0);
        if (ret <= 0) {
            printf("recv fail\n");
            break;
        }
        ret = p->proxy->SendData(p->protocol->requestClient, buf, ret);
        if (ret <= 0) {
            printf("send data fail\n");
            break;
        }
    }
    pthread_exit(0);
}

void* Proxy::excThreadReverse(void* peer)
{
    Peer* p;
    p = (struct Peer*)peer;
    char buf[MAX_SIZE];

    while (true)
    {
        int ret = recv(p->protocol->requestClient, buf, MAX_SIZE, 0);
        if (ret <= 0) {
            printf("recv fail2\n");
            break;
        }
        ret = p->proxy->SendData(p->protocol->webServer, buf, ret);
        if (ret <= 0) {
            printf("send fail 222\n");
            break;
        }
    }
    pthread_exit(0);
}

int Proxy::ExchangeData(void* peer)
{
    Peer* p;
    p = (struct Peer*)peer;
    pthread_t tid1, tid2;
    pthread_create(&tid1, NULL, excThreadReverse, p);

    pthread_create(&tid2, NULL, excThread, p);

    // pthread_cancel(tid1);
    // pthread_cancel(tid2);

    pthread_join(tid1, NULL);    
    pthread_join(tid2, NULL);
    

    printf("test....\n");
    // shutdown(p->protocol->webServer, 2);
    // shutdown(p->protocol->requestClient, 2);

    pthread_join(tid2, NULL);
    pthread_cancel(tid2);
    pthread_join(tid1, NULL);
   
    return 0;
}

void* Proxy::serverThread(void* peer)
{
    Peer* p;
    p = (struct Peer*)peer;
    char RecvBuf[MAX_SIZE] = { 0 };
    char SendBuf[MAX_SIZE] = { 0 };

    // receive http request from client
    int ret = p->proxy->RecvHTTPRequest(p->protocol->requestClient, RecvBuf, MAX_SIZE);
    if (ret == -1 || ret == 0) {
        close(p->protocol->requestClient);
    }
    
    // Connect to proxy
    if (strncmp("CONNECT ", RecvBuf, 8) == 0)
    {
        if (p->proxy->ConnectToServer(p->protocol->webServer, RecvBuf, ret) < 0)
        {
            close(p->protocol->requestClient);
            delete p;
            return 0;
        }
        if (p->proxy->PreResponse(p->protocol->requestClient) < 0)
        {
            close(p->protocol->requestClient);
        }
        p->proxy->ExchangeData(p);
    }
}

void Proxy::StartProxy() {
    if (InitServerSocket() == false)
    {
        printf("Fail to init server socket!\n");
        exit(-1);
    }
    int acceptSocket = -1;

    Protocol* protocol;
    
    
    while (true)
    {
        addr_len = sizeof(Client);
        acceptSocket = accept(serverSocket, (struct sockaddr*)&Client, &addr_len);
        protocol = new Protocol();
        protocol->requestClient = acceptSocket;
        printf("accepted connection from %s, port %d\n",
            inet_ntoa(Client.sin_addr),
            htons(Client.sin_port));

        Peer* peer = new Peer;
        peer->protocol = protocol;
        peer->proxy = this;

        pthread_t tid;
        int ret = pthread_create(&tid, NULL, serverThread, peer);
        if (ret != 0) {
            Error("Create thread error: ");
        }
    }
}

int main(int argc, char* argv[])
{
    cout
        << "Proxy Lite" << endl
        << "Version: 0.1" << endl
        << "Started..." << endl;
    
    Proxy proxy;
    proxy.StartProxy();

    return 0;
}
